﻿using System;
using System.Collections.Generic;
using System.Text;

namespace InkjetControl.plc
{
    public class ModbusRTU
    {

        /// <summary>
        /// 读取线圈指令
        /// </summary>
        private const byte COLR = 0x01;
        /// <summary>
        /// 读输入线圈指令
        /// </summary>
        private const byte INPR = 0x02;
        /// <summary>
        /// 读出寄存器内容
        /// </summary>
        private const byte REGR = 0x03;
        /// <summary>
        /// 读输入寄存器指令
        /// </summary>
        private const byte INRR = 0x04;
        /// <summary>
        /// 写单个线圈指令
        /// </summary>
        private const byte COLW = 0x05;
        /// <summary>
        /// 写单个寄存器指令
        /// </summary>
        private const byte CREGW = 0x06;
        /// <summary>
        /// 写多个寄存器指令
        /// </summary>
        private const byte MRGW = 0x10;
        /// <summary>
        /// 写多个线圈指令
        /// </summary>
        private const byte MCLW = 0x0F;

        /// <summary>
        /// 读取线圈
        /// </summary>
        /// <param name="addr"></param>
        /// <param name="coilID"></param>
        /// <param name="num"></param>
        public void ReadCoil(byte addr, ushort coilID, uint num)
        {
            GenerateCmdParams(addr, COLR, (byte)(coilID >> 8), (byte)coilID, (byte)(num >> 8), (byte)num);
        }

        /// <summary>
        /// 读输入线圈
        /// </summary>
        /// <param name="addr"></param>
        /// <param name="coilID"></param>
        /// <param name="num"></param>
        public void ReadInputCoil(byte addr, ushort coilID, uint num)
        {
            GenerateCmdParams(addr, INPR, (byte)(coilID >> 8), (byte)coilID, (byte)(num >> 8), (byte)num);
        }

        /// <summary>
        /// 读出寄存器内容
        /// </summary>
        /// <param name="addr"></param>
        /// <param name="regID"></param>
        /// <param name="num"></param>
        public void ReadRegister(byte addr, ushort regID, uint num)
        {
            GenerateCmdParams(addr, REGR, (byte)(regID >> 8), (byte)regID, (byte)(num >> 8), (byte)num);
        }

        /// <summary>
        /// 读输入寄存器
        /// </summary>
        /// <param name="addr"></param>
        /// <param name="regID"></param>
        /// <param name="num"></param>
        public void ReadInputRegister(byte addr, ushort regID, uint num)
        {
            GenerateCmdParams(addr, INRR, (byte)(regID >> 8), (byte)regID, (byte)(num >> 8), (byte)num);
        }

        /// <summary>
        /// 写单个寄存器
        /// </summary>
        public void WriteSingleRegister(byte addr, ushort regAddr, params byte[] data)
        {
            int len = 2;
            if (data.Length > 0)
            {
                len += data.Length;
            }
            byte[] tmpBuf = new byte[len];
            tmpBuf[0] = (byte)(regAddr >> 8);
            tmpBuf[1] = (byte)regAddr;
            if (len > 2)
            {
                Array.Copy(data, 0, tmpBuf, 2, data.Length);
            }

            GenerateCmdParams(addr, CREGW, tmpBuf);
        }

        /// <summary>
        /// 写多个寄存器
        /// </summary>
        /// <param name="addr">地址</param>
        /// <param name="regID">寄存器地址</param>
        /// <param name="num">寄存器个数</param>
        /// <param name="data">数据内容</param>
        public void WriteMultipleRegisters(byte addr, ushort regID, ushort num, params ushort[] data)
        {
            int len = 5;
            int dlen = data.Length;
            int j = 5;
            if (dlen > 0)
            {
                len += dlen * 2;
            }
            byte[] buf = new byte[len];
            buf[0] = (byte)(regID >> 8);
            buf[1] = (byte)regID;

            buf[2] = (byte)(num >> 8);
            buf[3] = (byte)num;

            buf[4] = (byte)(num * 2); //字节数
            for (int i = 0; i < dlen; i++)
            {
                ushort temp = data[i];
                buf[j++] = (byte)(temp >> 8);
                buf[j++] = (byte)temp;
            }
            GenerateCmdParams(addr, MRGW, buf);
        }

        /// <summary>
        /// 写单个线圈指令 数据内容（低在前高在后）
        /// </summary>
        /// <param name="addr">地址</param>
        /// <param name="coilId">线圈地址</param>
        /// <param name="data"> 数据内容（低在前高在后）</param>
        public void WriteSingleCoil(byte addr, ushort coilId, ushort data)
        {
            GenerateCmdParams(addr, COLW, (byte)(coilId >> 8), (byte)coilId, (byte)data, (byte)(data >> 8));
        }

        /// <summary>
        /// 写多个线圈指令
        /// </summary>
        /// <param name="addr">地址</param>
        /// <param name="coilId">线圈地址</param>
        /// <param name="num">线圈个数</param>
        /// <param name="data">数据内容（低在前高在后）</param>
        public void WriteMultipleCoils(byte addr, ushort coilId, uint num, params ushort[] data)
        {
            int len = 5;
            int dlen = data.Length;
            int dbuflen = data.Length * 2;
            int i = 0;
            int j = 5;
            if (dbuflen > 0)
            {
                len += dbuflen;
            }
            byte[] buf = new byte[len];
            buf[0] = (byte)(coilId >> 8);
            buf[1] = (byte)coilId;

            buf[2] = (byte)(num >> 8);
            buf[3] = (byte)num;

            buf[4] = (byte)dbuflen;

            while (i < dlen)
            {
                ushort temp = data[i];
                buf[j++] = (byte)temp;
                buf[j++] = (byte)(temp >> 8);
                i++;
            }

            GenerateCmdParams(addr, MCLW, buf);
        }

        private static readonly byte[] AuchCRCHi = {
            0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
            0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
            0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
            0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
            0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
            0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
            0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
            0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
            0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
            0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
            0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
            0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
            0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
            0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
            0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
            0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
            0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
            0x40
        };
        private static readonly byte[] AuchCRCLo = {
            0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
            0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
            0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
            0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
            0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
            0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
            0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
            0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
            0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
            0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
            0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
            0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
            0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
            0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
            0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
            0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
            0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
            0x40
        };

        private static ushort CRC16MODBUS(byte[] data, int len)
        {
            int i = 0;
            byte crcHi = 0xFF;
            byte crcLo = 0xFF;
            ushort auchIndex;

            while (len-- > 0)
            {
                auchIndex = (ushort)(crcLo ^ data[i++]);
                crcLo = (byte)(crcHi ^ AuchCRCHi[auchIndex]);
                crcHi = AuchCRCLo[auchIndex];
            }
            return (ushort)((crcHi << 8) | crcLo);
        }

        internal void GenerateCmdParams(byte addr, byte functionCode, params byte[] data)
        {
            int len = 4;
            if (data != null)
            {
                len += data.Length;
            }
            byte[] cmdData = new byte[len];
            cmdData[0] = addr;
            cmdData[1] = functionCode;

            //Array.Reverse(data);
            Array.Copy(data, 0, cmdData, 2, data.Length);

            ushort crcCode = CRC16MODBUS(cmdData, len);
            cmdData[len - 1] = (byte)(crcCode >> 8);
            cmdData[len - 2] = (byte)crcCode;
            //
            //CmdParamsSenderHandler?.Invoke(cmdData);
        }

        public void ResponseData(byte[] data, int len)
        {
            ushort crcCode = CRC16MODBUS(data, len - 2);
            ushort dataCrc = (ushort)((data[len - 1] >> 8) | data[len - 2]);
            if (crcCode == dataCrc)
            {
                byte devID = data[0];
                byte funcCode = data[1];
                ushort val;

                ushort colAddr;
                ushort numCol;  
                ushort regAddr;
                ushort numReg;

                byte bufLen;
                byte[] dataBuf;

                switch (funcCode)
                {
                    case COLR:
                    case INPR:
                        bufLen = data[2];
                        dataBuf = new byte[bufLen];
                        Array.Copy(data, 3, dataBuf, 0, bufLen);
                        break;
                    case REGR:
                        bufLen = data[2];
                        dataBuf = new byte[bufLen];
                        Array.Copy(data, 3, dataBuf, 0, bufLen);
                        break;
                    case INRR:
                        bufLen = data[2];
                        dataBuf = new byte[bufLen];
                        Array.Copy(data, 3, dataBuf, 0, bufLen);
                        break;
                    case COLW:
                        colAddr = (ushort)((data[2] >> 8) | data[3]);
                        val = (ushort)((data[5] >> 8) | data[4]);
                        break;
                    case CREGW:
                        regAddr = (ushort)((data[2] >> 8) | data[3]);
                        val = (ushort)((data[4] >> 8) | data[5]);
                        break;
                    case MRGW:
                        regAddr = (ushort)((data[2] >> 8) | data[3]);
                        numReg = (ushort)((data[4] >> 8) | data[5]);
                        break;
                    case MCLW:
                        colAddr = (ushort)((data[2] >> 8) | data[3]);
                        numCol = (ushort)((data[4] >> 8) | data[5]);
                        break;
                    default:
                        break;
                }
            }
        }
    }
}
